

package com.googlecode.flac4j.format;


import com.googlecode.jinahya.io.BitInput;
import com.googlecode.jinahya.io.BitOutput;

import java.io.IOException;


/**
 *
 * @author Jin Kwon <jinahya at gmail.com>
 */
public final class FormatHelper {


    public static void readBytes(final BitInput input, final byte[] bytes)
        throws IOException {

        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) input.readUnsignedInt(8);
        }
    }


    public static void writeBytes(final BitOutput output, final byte[] bytes)
        throws IOException {

        for (int i = 0; i < bytes.length; i++) {
            output.writeUnsignedInt(8, bytes[i] & 0xFF);
        }
    }


    public static byte[] intToBytes(int value) {

        final byte[] bytes = new byte[4];
        for (int i = bytes.length - 1; i >= 0; i--) {
            bytes[i] = (byte) (value & 0xFF);
            value >>>= 8;
        }

        return bytes;
    }


    /**
     * Converts given <code>bytes</code> to an int value.
     *
     * @param bytes bytes
     * @return int value.
     */
    public static int bytesToInt(final byte[] bytes) {

        if (bytes == null) {
            throw new NullPointerException("null bytes");
        }

        if (bytes.length != 4) {
            throw new IllegalArgumentException(
                "byte.length(" + bytes.length + ") != 4");
        }

        int value = 0x00;
        for (int i = 0; i < bytes.length; i++) {
            value <<= 8;
            value |= bytes[i];
        }

        return value;
    }


    /**
     * Converts given <code>value</code> to a byte array.
     *
     * @param value value
     * @return a byte array
     */
    public static byte[] longToBytes(long value) {

        final byte[] bytes = new byte[8];
        for (int i = bytes.length - 1; i >= 0; i--) {
            bytes[i] = (byte) (value & 0xFF);
            value >>>= 8;
        }

        return bytes;
    }


    /**
     * Converts given <code>bytes</code> to a long value.
     *
     * @param bytes bytes
     * @return long value
     */
    public static long bytesToLong(final byte[] bytes) {

        if (bytes == null) {
            throw new NullPointerException("null bytes");
        }

        if (bytes.length != 8) {
            throw new IllegalArgumentException(
                "byte.length(" + bytes.length + ") != 8");
        }

        long value = 0x00L;
        for (int i = 0; i < bytes.length; i++) {
            value <<= 8;
            value |= bytes[i];
        }

        return value;
    }


    public static int log2(final long product) {
        return (int) (Math.log(product) / Math.log(2.0d));
    }


    public static int readIntAsUTF8(final BitInput input) throws IOException {

        int value = input.readUnsignedInt(8);

        if ((value & 0x80) == 0) { // 0xxxxxxx
            return value;
        }

        int count;

        if (value >> 5 == 6) { // 110xxxxx
            value &= 0x1F;
            count = 1;
        } else if (value >> 4 == 0x0E) { // 1110xxxx
            value &= 0x0F;
            count = 2;
        } else if (value >> 3 == 0x1E) { // 11110xxx
            value &= 0x07;
            count = 3;
        } else if (value >> 2 == 0x3E) { // 111110xx
            value &= 0x03;
            count = 4;
        } else if (value >> 1 == 0x7E) { // 1111110x
            value &= 0x01;
            count = 5;
        } else {
            throw new IOException("illegal UTF-8 byte sequence");
        }

        for (int i = 0; i < count; i++) {
            value <<= 6;
            value |= (input.readUnsignedInt(8) & 0x3F);
        }

        return value;
    }


    public static void writeIntAsUTF8(final BitOutput output, final int value)
        throws IOException {

        if (output == null) {
            throw new NullPointerException("null output");
        }

        if (value < 0) {
            throw new IllegalArgumentException("value(" + value + ") < 0");
        }

        int count;
        if (value <= 0x7F) {
            output.writeUnsignedInt(8, value);
            return;
        } else if (value <= 0x7FF) {
            count = 1;
        } else if (value <= 0xFFFF) {
            count = 2;
        } else if (value <= 0x1FFFFF) {
            count = 3;
        } else if (value <= 0x3FFFFFF) {
            count = 4;
        } else {
            assert value <= 0x7FFFFFFF;
            count = 5;
        }

        switch (count) {
            case 1:
                output.writeUnsignedInt(8, 0xC0 | (value >> (count * 6)));
                break;
            case 2:
                output.writeUnsignedInt(8, 0xE0 | (value >> (count * 6)));
                break;
            case 3:
                output.writeUnsignedInt(8, 0xF0 | (value >> (count * 6)));
                break;
            case 4:
                output.writeUnsignedInt(8, 0xF8 | (value >> (count * 6)));
                break;
            default:
                assert count == 5;
                output.writeUnsignedInt(8, 0xFC | (value >> (count * 6)));
                break;
        }

        for (int i = count - 1; i >= 0; i--) {
            output.writeUnsignedInt(8, 0x80 | ((value >> (i * 6)) & 0x3F));
        }
    }


    public static long readLongAsUTF8(final BitInput input) throws IOException {

        long value = input.readUnsignedLong(8);

        if ((value & 0x80) == 0) { // 0xxxxxxx
            return value;
        }

        int count;

        if (value >> 5 == 6) { // 110xxxxx
            value &= 0x1F;
            count = 1;
        } else if (value >> 4 == 0x0E) { // 1110xxxx
            value &= 0x0F;
            count = 2;
        } else if (value >> 3 == 0x1E) { // 11110xxx
            value &= 0x07;
            count = 3;
        } else if (value >> 2 == 0x3E) { // 111110xx
            value &= 0x03;
            count = 4;
        } else if (value >> 1 == 0x7E) { // 1111110x
            value &= 0x01;
            count = 5;
        } else if (value >> 0 == 0xFE) { // 11111110
            value &= 0x00L;
            count = 6;
        } else {
            throw new IOException("illegal UTF-8 byte sequence");
        }

        for (int i = 0; i < count; i++) {
            value <<= 6;
            value |= (input.readUnsignedInt(8) & 0x3F);
        }

        return value;
    }


    public static void writeLongAsUTF8(final BitOutput output, final long value)
        throws IOException {

        if (output == null) {
            throw new NullPointerException("null output");
        }

        if (value < 0L) {
            throw new IllegalArgumentException("value(" + value + ") < 0L");
        }

        int count;
        if (value <= 0x7FL) {
            output.writeUnsignedLong(8, value);
            return;
        } else if (value <= 0x7FFL) {
            count = 1;
        } else if (value <= 0xFFFFL) {
            count = 2;
        } else if (value <= 0x1FFFFFL) {
            count = 3;
        } else if (value <= 0x3FFFFFFL) {
            count = 4;
        } else if (value <= 0x7FFFFFFFL) {
            count = 5;
        } else {
            count = 6;
        }

        switch (count) {
            case 1:
                output.writeUnsignedLong(8, 0xC0 | (value >> (count * 6)));
                break;
            case 2:
                output.writeUnsignedLong(8, 0xE0 | (value >> (count * 6)));
                break;
            case 3:
                output.writeUnsignedLong(8, 0xF0 | (value >> (count * 6)));
                break;
            case 4:
                output.writeUnsignedLong(8, 0xF8 | (value >> (count * 6)));
                break;
            case 5:
                output.writeUnsignedLong(8, 0xFC | (value >> (count * 6)));
                break;
            default:
                assert count == 6;
                output.writeUnsignedLong(8, 0xFE); // 11111110
                break;
        }

        for (int i = count - 1; i >= 0; i--) {
            output.writeUnsignedLong(8, 0x80 | ((value >> (i * 6)) & 0x3F));
        }
    }


    /**
     * Creates a new instance.
     */
    private FormatHelper() {
        super();
    }


}

